class Constants {
}
Object.defineProperty(Constants, "Serial", {
    enumerable: true,
    configurable: true,
    writable: true,
    value: 0
});
Object.defineProperty(Constants, "Parallel", {
    enumerable: true,
    configurable: true,
    writable: true,
    value: 1
});
Object.defineProperty(Constants, "Node", {
    enumerable: true,
    configurable: true,
    writable: true,
    value: 2
});
Object.defineProperty(Constants, "GRAPH_DIRECTION_L2R", {
    enumerable: true,
    configurable: true,
    writable: true,
    value: 0
});
Object.defineProperty(Constants, "GRAPH_DIRECTION_T2B", {
    enumerable: true,
    configurable: true,
    writable: true,
    value: 1
});

/**
 * 计算图形
 * @param root 节点树根节点
 * @param config 配置
 * @returns
 */
function toCalcGraphData(root, config = {}) {
    const opts = Object.assign({
        space: [70, 20], size: [100, 50], offset: [0, 0], arrow: 5,
        direction: Constants.GRAPH_DIRECTION_L2R, compact: false, debug: false,
        optimizeData: true
    }, config);
    const dnodes = [];
    // 简化空节点
    if (opts.optimizeData)
        toSimplifyNodes(root.children || [], opts.compact);
    const offset = [0, 0];
    offset[0] += opts.offset[0] + opts.space[0];
    offset[1] += opts.offset[1] + opts.space[1];
    // 计算坐标
    const rootNode = toCalcNodePosition.call(root, dnodes, offset, opts);
    const dsize = [0, 0];
    dsize[0] += rootNode.size[0] + offset[0] * 2;
    dsize[1] += rootNode.size[1] + offset[1] * 2;
    const dline = toCalcLine(rootNode, opts, dsize);
    const dlines = !opts.debug ? [dline] : dline.split('M').filter(d => d.trim()).map(d => `M${d}`);
    return { dnodes, dlines, dsize, root: rootNode };
}
/**
 * 计算节点坐标
 * @param this
 * @param offset 起始偏移量
 * @param opts 绘制对象
 * @returns
 */
function toCalcNodePosition(dnodes, offset, opts) {
    this.position = [...offset];
    if (this.type === Constants.Node) {
        const [w, h] = opts.size;
        if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
            this.size = [w, h, w, 1, 1];
        }
        else {
            this.size = [w, h, h, 1, 1];
        }
        dnodes.push(this);
        return this;
    }
    this.size = [0, 0, 0, 0, 0];
    if (!this.children || this.children.length === 0)
        return this;
    let [x, y] = offset;
    // const size = [宽度,最高高度，第1层高度]
    if (this.type === Constants.Parallel) {
        let offset_space = 0;
        for (const child of this.children) {
            child.pname = this.name;
            toCalcNodePosition.call(child, dnodes, [x, y], opts);
            if (child.size[0] === 0)
                continue;
            offset_space = opts.direction === Constants.GRAPH_DIRECTION_T2B
                ? opts.space[0] : opts.space[1];
            // 盒子大小 宽度,最高高度，第1层高度
            if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
                this.size[0] += child.size[0] + offset_space;
                this.size[1] = Math.max(this.size[1], child.size[1]);
            }
            else {
                this.size[0] = Math.max(this.size[0], child.size[0]);
                this.size[1] += child.size[1] + offset_space;
            }
            this.size[2] += child.size[2] + offset_space;
            this.size[3] += child.size[3];
            this.size[4] += child.size[4];
            // y = Math.max(child.position[1] + offset_space_y, y);
            if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
                if (child.position[0] + offset_space > x) {
                    this.position[0] = Math.max(offset[0], offset[0] + (child.position[0] - offset[0]) / 2);
                }
                x += child.size[2] + offset_space;
            }
            else {
                if (child.position[1] + offset_space > y) {
                    this.position[1] = Math.max(offset[1], offset[1] + (child.position[1] - offset[1]) / 2);
                }
                y += child.size[2] + offset_space;
            }
            continue;
        }
        if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
            this.size[0] -= offset_space;
        }
        else {
            this.size[1] -= offset_space;
        }
        this.size[2] -= offset_space;
        return this;
    }
    if (this.type === Constants.Serial) {
        let max_pos = 0;
        let offset_space = 0;
        for (const child of this.children) {
            child.pname = this.name;
            toCalcNodePosition.call(child, dnodes, [x, y], opts);
            if (child.size[0] === 0)
                continue;
            offset_space = opts.direction === Constants.GRAPH_DIRECTION_T2B
                ? opts.space[1] : opts.space[0];
            // 盒子大小 宽度,最高高度，第1层高度
            if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
                this.size[0] = Math.max(this.size[0], child.size[0]);
                this.size[1] += child.size[1] + offset_space;
            }
            else {
                this.size[0] += child.size[0] + offset_space;
                this.size[1] = Math.max(this.size[1], child.size[1]);
            }
            this.size[2] = Math.max(this.size[2], child.size[2]);
            // 坐标
            if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
                max_pos = Math.max(max_pos, child.position[0]);
                y += child.size[1] + offset_space;
            }
            else {
                x += child.size[0] + offset_space;
                max_pos = Math.max(max_pos, child.position[1]);
            }
            if (this.size[3] === 0)
                this.size[3] = child.size[3];
            this.size[4] = child.size[4];
            // y0 = Math.max(y0, child.position[1]);
            continue;
        }
        if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
            this.size[1] -= offset_space;
        }
        else {
            this.size[0] -= offset_space;
        }
        /** 修正坐标 */
        if (max_pos <= 0)
            return this;
        // 坐标是0:X, 1:Y
        const pos = opts.direction === Constants.GRAPH_DIRECTION_T2B ? 0 : 1;
        this.position[pos] = max_pos;
        for (const child of this.children) {
            const offset = max_pos - child.position[pos];
            let offset_x = opts.direction === Constants.GRAPH_DIRECTION_T2B ? offset : 0;
            let offset_y = opts.direction === Constants.GRAPH_DIRECTION_T2B ? 0 : offset;
            if (offset > 0)
                toFixOffsetCalcNodePosition.call(child, offset_x, offset_y);
        }
        return this;
    }
    return this;
}
/**
 * 递归修正Y轴偏移量
 * @param this 节点
 * @param offset_x X轴偏移量
 * @param offset_y Y轴偏移量
 */
function toFixOffsetCalcNodePosition(offset_x, offset_y) {
    this.position[0] += offset_x;
    this.position[1] += offset_y;
    for (const child of this.children) {
        toFixOffsetCalcNodePosition.call(child, offset_x, offset_y);
    }
}
/**
 * 计算节点线
 * @param this
 * @param opts
 * @param graphSize 图形大小
 * @returns
 */
function toCalcLine(dnode, opts, graphSize) {
    let line = Constants.GRAPH_DIRECTION_T2B === opts.direction
        ? toCalcLineT2B.call(dnode, opts, graphSize)
        : toCalcLineL2R.call(dnode, opts, graphSize);
    line += toCalcLeanLine(dnode, opts);
    return line;
}
function toCalcLineL2R(opts, graphSize) {
    let line = '';
    // 画箭头
    if (this.type === Constants.Node && this.pname) {
        let [x, y] = this.position;
        // 起始点小于偏移开始节点, 无需画箭头
        if (x <= opts.offset[0] + opts.space[0])
            return line;
        const arrow = opts.arrow;
        if (arrow <= 0)
            return line;
        y += this.size[1] / 2;
        line += `M${x - arrow},${y - arrow} L${x - 1},${y} L${x - arrow},${y + arrow} `;
        return line;
    }
    if (!this.children || this.children.length === 0)
        return line;
    let childNext;
    let i = 0;
    for (const child of this.children) {
        // 空盒子
        if (child.size[0] === 0)
            continue;
        // 递归画子节点线
        line += toCalcLine(child, opts, graphSize);
        // 弟弟节点
        childNext = this.children[++i];
        // 普通节点不用画线
        if (this.type === Constants.Node)
            continue;
        // 父级盒子与子级盒子一样, 无需画该子级本身的线条
        if (this.size[0] === child.size[0] && this.size[1] === child.size[1])
            continue;
        // 并行情况
        if (this.type === Constants.Parallel) {
            // 画左边 -|
            const position = [...child.position];
            position[0] -= opts.space[0] / 2;
            position[1] += opts.size[1] / 2;
            let [x, y] = position;
            // 是否是多子节点
            // 是紧凑模型时或开始位置大于偏移位置
            if (opts.compact || x > opts.offset[0] + opts.space[0]) {
                const move = [x + opts.space[0] / 2, y].join(',');
                if (child.size[3] === 1) {
                    // 1.移动画线坐标
                    line += `M${move} `;
                    // 2.画横线
                    line += `L${position} `;
                }
                else {
                    line += `M${position} `;
                }
                // 3.画竖线
                if (childNext && childNext.size[0] > 0) {
                    const position = [...childNext.position];
                    position[0] -= opts.space[0] / 2;
                    position[1] += opts.size[1] / 2;
                    line += `L${position} `;
                }
            }
            // 画右边 -|
            x += opts.space[0] / 2;
            // 推算下个节点X坐标 = 本级节点X(包含自身宽度) + 间距 +下个盒子宽度 + 图偏移量;
            const x_next = x + this.size[0] + opts.space[0] + opts.offset[0];
            if (opts.compact || graphSize[0] > x_next) {
                // 1.移动画线坐标
                const move = [x + child.size[0], y];
                if (child.size[4] !== 1)
                    move[0] += opts.space[0] / 2;
                line += `M${move.join(',')} `;
                // 2.画横线
                line += `L${[x + this.size[0] + opts.space[0] / 2, y].join(',')} `;
                // 3.画竖线
                if (childNext) {
                    const position = [...childNext.position];
                    position[0] += this.size[0] + opts.space[0] / 2;
                    position[1] += opts.size[1] / 2;
                    line += `L${position} `;
                }
            }
            continue;
        }
        if (this.type === Constants.Serial && childNext) {
            // 相邻节点为并行节点-不画串行先
            // if (child.type === childNext.type && child.type === Constants.Parallel) continue;
            // 起始坐标 >= 结束坐标 不用划线(无效- 不应该存在)
            // if (child.position[0] >= childNext.position[0]) continue;
            let [x, y] = child.position;
            y += opts.size[1] / 2;
            x += child.size[0];
            // 是否是多个子节点
            if (child.size[4] !== 1)
                x += opts.space[0] / 2;
            const start = [x, y].join(',');
            x = childNext.position[0];
            // 是否是多个子节点
            if (childNext.size[3] !== 1)
                x -= opts.space[0] / 2;
            const end = [x, y].join(',');
            // console.log(this.name, child.name, childNext.name, `M${start} L${end} `)
            line += `M${start} L${end} `;
            continue;
        }
    }
    return line;
}
function toCalcLineT2B(opts, graphSize) {
    let line = '';
    // 画箭头
    if (this.type === Constants.Node && this.pname) {
        let [x, y] = this.position;
        // 起始点小于偏移开始节点, 无需画箭头
        if (y <= opts.offset[1] + opts.space[1])
            return line;
        const arrow = opts.arrow;
        if (arrow <= 0)
            return line;
        x += this.size[0] / 2;
        line += `M${x - arrow},${y - arrow} L${x},${y - 1} L${x + arrow},${y - arrow} `;
        return line;
    }
    if (!this.children || this.children.length === 0)
        return line;
    let childNext;
    let i = 0;
    for (const child of this.children) {
        // 空盒子
        if (child.size[0] === 0)
            continue;
        // 递归画子节点线
        line += toCalcLine(child, opts, graphSize);
        // 弟弟节点
        childNext = this.children[++i];
        // 普通节点不用画线
        if (this.type === Constants.Node)
            continue;
        // 父级盒子与子级盒子一样, 无需画该子级本身的线条
        if (this.size[0] === child.size[0] && this.size[1] === child.size[1])
            continue;
        // 并行情况
        if (this.type === Constants.Parallel) {
            // 画左边 -|
            const position = [...child.position];
            position[0] += opts.size[0] / 2;
            position[1] -= opts.space[1] / 2;
            let [x, y] = position;
            // 是否是多子节点
            // 是紧凑模型时或开始位置大于偏移位置
            if (opts.compact || y > opts.offset[1] + opts.space[1]) {
                const move = [x, y + opts.space[1] / 2].join(',');
                if (child.size[3] === 1) {
                    // 1.移动画线坐标
                    line += `M${move} `;
                    // 2.画横线
                    line += `L${position} `;
                }
                else {
                    line += `M${position} `;
                }
                // 3.画竖线
                if (childNext && childNext.size[0] > 0) {
                    const position = [...childNext.position];
                    position[0] += opts.size[0] / 2;
                    position[1] -= opts.space[1] / 2;
                    line += `L${position} `;
                }
            }
            // 画右边 -|
            y += opts.space[1] / 2;
            // 推算下个节点X坐标 = 本级节点X(包含自身宽度) + 间距 +下个盒子宽度 + 图偏移量;
            const y_next = y + this.size[1] + opts.space[1] + opts.offset[1];
            if (opts.compact || graphSize[1] > y_next) {
                // 1.移动画线坐标
                const move = [x, y + child.size[1]];
                if (child.size[4] !== 1)
                    move[1] += opts.space[1] / 2;
                line += `M${move.join(',')} `;
                // 2.画横线
                line += `L${[x, y + this.size[1] + opts.space[1] / 2].join(',')} `;
                // 3.画竖线
                if (childNext) {
                    const position = [...childNext.position];
                    position[1] += this.size[1] + opts.space[1] / 2;
                    position[0] += opts.size[0] / 2;
                    line += `L${position} `;
                }
            }
            continue;
        }
        if (this.type === Constants.Serial && childNext) {
            // 相邻节点为并行节点-不画串行先
            // if (child.type === childNext.type && child.type === Constants.Parallel) continue;
            // 起始坐标 >= 结束坐标 不用划线(无效- 不应该存在)
            // if (child.position[0] >= childNext.position[0]) continue;
            let [x, y] = child.position;
            x += opts.size[0] / 2;
            y += child.size[1];
            // 是否是多个子节点
            if (child.size[4] !== 1)
                y += opts.space[1] / 2;
            const start = [x, y].join(',');
            y = childNext.position[1];
            // 是否是多个子节点
            if (childNext.size[3] !== 1)
                y -= opts.space[1] / 2;
            const end = [x, y].join(',');
            // console.log(this.name, child.name, childNext.name, `M${start} L${end} `)
            line += `M${start} L${end} `;
            continue;
        }
    }
    return line;
}
/**
 * 添加依赖先
 * @param dnodes 节点
 * @param opts 配置
 * @returns 依赖线条
 */
function toCalcLeanLine(node, opts) {
    const dnodes = toExtractNodes(node);
    const map = {};
    const leans = [];
    let leanline = '';
    for (const dnode of dnodes) {
        if (dnode.type !== Constants.Node)
            continue;
        map[dnode.name] = dnode;
        if (typeof dnode.lean !== 'string')
            continue;
        const lean = dnode.lean.replace(/\s/g, '');
        if (lean.length === 0)
            continue;
        const leannames = lean.split(',');
        for (const leanname of leannames) {
            leans.push([leanname, dnode.name]);
        }
    }
    if (leans.length === 0)
        return leanline;
    for (const lean of leans) {
        // 没有找到依赖的节点
        if (!map[lean[0]])
            continue;
        const n1 = map[lean[0]]; // 开始节点
        const n2 = map[lean[1]]; // 结束节点
        if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
            // 从上到下
            let [x, y] = n1.position;
            x += n1.size[0] / 2;
            y += n1.size[1];
            const start = [x, y].join(',');
            [x, y] = n2.position;
            x += n2.size[0] / 2;
            const end = [x, y].join(',');
            leanline += `M${start} L${end} `;
        }
        else {
            // 从左到右
            let [x, y] = n1.position;
            x += n1.size[0];
            y += n1.size[1] / 2;
            const start = [x, y].join(',');
            [x, y] = n2.position;
            y += n2.size[1] / 2;
            const end = [x, y].join(',');
            leanline += `M${start} L${end} `;
        }
    }
    return leanline;
}
/**
 * 简化节点
 * @param nodes 节点列表
 * @returns 节点列表
 */
function toSimplifyNodes(nodes, compact = false) {
    if (nodes.length === 0)
        return nodes;
    const array = nodes.splice(0);
    let node;
    for (node of array) {
        node = toSimplifyNode.call(node, compact);
        if (node)
            nodes.push(node);
    }
    return nodes;
}
function toSimplifyNode(compact = false) {
    if (this.type === Constants.Node)
        return this;
    if (!this.children || this.children.length === 0)
        return undefined;
    if (this.children.length === 1)
        return toSimplifyNode.call(this.children[0]);
    for (let i = 0; i < this.children.length; i++) {
        let child = this.children[i];
        if (child.type === Constants.Node)
            continue;
        const rchild = toSimplifyNode.call(child);
        if (rchild !== child || this.type === child.type) {
            if (!child.children || child.children.length === 0) {
                this.children.splice(i, 1);
                i--;
                continue;
            }
            this.children.splice(i, 1, ...child.children);
            i--;
            continue;
        }
    }
    return this;
}
function toExtractNodes(dnode, dnodes = []) {
    if (dnode.type === Constants.Node) {
        dnodes.push(dnode);
        return dnodes;
    }
    for (const child of dnode.children) {
        toExtractNodes(child, dnodes);
    }
    return dnodes;
}

/**
 * 模拟节点数组
 * @param max 最大模拟节点数
 * @param compact 是否紧凑节点
 * @returns 节点集合
 */
function mockNodes(max = 20, weight = 0) {
    var _a;
    let index = 0;
    let count = 0;
    const nodes = [];
    function mockNode() {
        let type = (index === 0 ? Constants.Serial : Math.floor(Math.random() * (3 + weight)));
        if (type >= 2)
            type = 2;
        const name = ['S', 'P', 'N'][type] + index++;
        const node = new MockXNode(type, name);
        nodes.push(node);
        if (type === Constants.Node) {
            count++;
            return node;
        }
        const len = Math.floor(Math.random() * 7) + 3;
        for (let i = 0; i < len; i++) {
            if (count >= max)
                break;
            node.children.push(mockNode());
        }
        return node;
    }
    mockNode();
    while (max > count) {
        (_a = nodes[0].children) === null || _a === void 0 ? void 0 : _a.push(mockNode());
    }
    return nodes;
}
/** 模拟基础数据节点 */
class MockXNode {
    constructor(type, name) {
        Object.defineProperty(this, "type", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "children", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: []
        });
        // 计算出来的值
        Object.defineProperty(this, "size", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: [0, 0, 0, 0, 0]
        });
        Object.defineProperty(this, "position", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: [0, 0]
        });
        this.type = type;
        this.name = name;
    }
}

export { Constants, MockXNode, mockNodes, toCalcGraphData, toCalcLeanLine, toCalcLine, toSimplifyNodes };
